Obafemi Emmanuel

Mastering React Hooks: Advanced Hooks & Custom Hooks

Published 3 months ago

React Hooks revolutionized the way developers manage state and side effects in functional components. While useState and useEffect are commonly used, advanced hooks like useReducer, useRef, useMemo, and useCallback provide deeper control over state management, performance optimization, and DOM manipulation. Additionally, creating custom hooks allows us to reuse logic efficiently. In this blog, we will explore these advanced hooks in detail.


1. useReducer – Alternative to useState

useReducer is a powerful hook that provides an alternative to useState, especially when dealing with complex state logic. It follows a reducer pattern similar to Redux, where actions dictate state changes.


Syntax:

const [state, dispatch] = useReducer(reducer, initialState);

Example:

import React, { useReducer } from "react";

const initialState = { count: 0 };
const reducer = (state, action) => {
    switch (action.type) {
        case "increment":
            return { count: state.count + 1 };
        case "decrement":
            return { count: state.count - 1 };
        default:
            return state;
    }
};

function Counter() {
    const [state, dispatch] = useReducer(reducer, initialState);
    return (
        <div>
            <p>Count: {state.count}</p>
            <button onClick={() => dispatch({ type: "increment" })}>+</button>
            <button onClick={() => dispatch({ type: "decrement" })}>-</button>
        </div>
    );
}

export default Counter;

When to Use useReducer?

  • When state transitions depend on previous state.
  • When dealing with multiple sub-values in state.
  • When state logic is complex and involves multiple actions.

2. useRef – Accessing DOM Elements

useRef allows direct interaction with DOM elements and persists values across renders without causing re-renders.


Example:

import React, { useRef } from "react";

function InputFocus() {
    const inputRef = useRef(null);

    const focusInput = () => {
        inputRef.current.focus();
    };

    return (
        <div>
            <input ref={inputRef} type="text" placeholder="Type here..." />
            <button onClick={focusInput}>Focus Input</button>
        </div>
    );
}

export default InputFocus;

When to Use useRef?

  • Accessing and manipulating DOM elements.
  • Storing values that should persist across renders without causing re-renders.
  • Holding references to timers or event listeners.

3. useMemo – Performance Optimization

useMemo memoizes a value, preventing expensive recalculations when dependencies have not changed.


Example:

import React, { useState, useMemo } from "react";

function ExpensiveComputation({ num }) {
    const expensiveValue = useMemo(() => {
        console.log("Computing...");
        return num * 10;
    }, [num]);

    return <p>Computed Value: {expensiveValue}</p>;
}

function App() {
    const [count, setCount] = useState(0);
    return (
        <div>
            <ExpensiveComputation num={count} />
            <button onClick={() => setCount(count + 1)}>Increment</button>
        </div>
    );
}

export default App;

When to Use useMemo?

  • To optimize performance when computing expensive values.
  • Avoid unnecessary recalculations in functional components.

4. useCallback – Preventing Unnecessary Re-renders

useCallback memoizes functions, preventing their recreation unless dependencies change.


Example:

import React, { useState, useCallback } from "react";

function Child({ handleClick }) {
    console.log("Child component re-rendered");
    return <button onClick={handleClick}>Click Me</button>;
}

function Parent() {
    const [count, setCount] = useState(0);

    const memoizedCallback = useCallback(() => {
        console.log("Button clicked!");
    }, []);

    return (
        <div>
            <p>Count: {count}</p>
            <button onClick={() => setCount(count + 1)}>Increment</button>
            <Child handleClick={memoizedCallback} />
        </div>
    );
}

export default Parent;

When to Use useCallback?

  • When passing functions as props to memoized child components.
  • To prevent unnecessary re-creations of functions.

5. Creating Custom Hooks

Custom hooks allow you to extract and reuse logic across multiple components.


Example: Custom Hook for Fetching Data

import { useState, useEffect } from "react";

function useFetch(url) {
    const [data, setData] = useState(null);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
        fetch(url)
            .then(response => response.json())
            .then(data => {
                setData(data);
                setLoading(false);
            });
    }, [url]);

    return { data, loading };
}

export default useFetch;

Using the Custom Hook

import React from "react";
import useFetch from "./useFetch";

function UserList() {
    const { data, loading } = useFetch("https://jsonplaceholder.typicode.com/users");

    if (loading) return <p>Loading...</p>;
    return (
        <ul>
            {data.map(user => (
                <li key={user.id}>{user.name}</li>
            ))}
        </ul>
    );
}

export default UserList;

When to Use Custom Hooks?

  • When logic needs to be reused across multiple components.
  • To abstract complex functionality into a separate function.

Conclusion

Advanced React hooks such as useReducer, useRef, useMemo, useCallback, and custom hooks enable developers to optimize performance, manage state effectively, and reuse logic efficiently. Understanding these hooks is essential for writing scalable and maintainable React applications. Try integrating them into your next project to take full advantage of React’s powerful hooks system!


Leave a Comment


Choose Colour